Skip to content

Narrow inner expression type when cast expression is used in strict/loose comparison#5665

Open
phpstan-bot wants to merge 1 commit into
phpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-ed24szz
Open

Narrow inner expression type when cast expression is used in strict/loose comparison#5665
phpstan-bot wants to merge 1 commit into
phpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-ed24szz

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When a cast expression like (string)$x is used in a comparison ((string)$x !== ''), PHPStan correctly narrows the type of the cast result but does not propagate the narrowing back to the inner expression $x. For example, with $x: string|null, (string)$x !== '' should narrow $x to non-empty-string (removing null since (string)null === ''), but previously $x remained string|null.

Changes

  • src/Analyser/TypeSpecifier.php:

    • Added Cast\String_ handling in specifyTypesForConstantStringBinaryExpression: when (string)$x is compared to a constant string, propagates narrowing to $x. For empty string comparison, removes null, false, and '' from $x's type. For non-empty string comparisons in true context, removes null and false.
    • Added Cast\Int_ and Cast\Double handling in specifyTypesForConstantBinaryExpression: when (int)$x or (float)$x is compared to a constant, removes null and false from $x's type (only when comparison with zero value in false context, or non-zero value in true context).
    • Extended resolveIdentical normalization to also swap Cast expressions to the left side (matching existing FuncCall normalization), ensuring reversed orderings like '' !== (string)$x work correctly.
    • Added inner expression narrowing in resolveEqual's == '' handler for Cast\String_ expressions, fixing loose comparison narrowing.
  • Analogous cases probed:

    • (bool) cast: already works correctly through existing specifyTypesForConstantBinaryExpressionspecifyTypesInCondition flow (verified with test)
    • (int) cast: fixed, narrowing now propagates for (int)$x !== 0 and (int)$x === 5 patterns
    • (float) cast: fixed, same pattern as int cast
    • Loose comparisons (!=, ==): fixed for string casts via resolveEqual handler

Root cause

The TypeSpecifier creates type specifications (narrowing) for the expression used in a comparison, but when that expression is a cast like (string)$x, the narrowing was only applied to the cast result — not to the inner expression $x. The fix adds explicit handlers that detect cast expressions in comparisons and create additional type specifications for the inner expression based on the semantics of the cast (e.g., (string)null === '', (int)null === 0).

Test

  • tests/PHPStan/Analyser/nsrt/bug-8231.php: regression test for the reported bug with (string) cast comparisons including !==, ===, reversed ordering, and various type combinations (string|null, int|null, string|false, bool|null)
  • tests/PHPStan/Analyser/nsrt/bug-8231-analogous.php: tests for analogous cases with (int) cast, (float) cast, (bool) cast (already working), non-zero constant comparisons, reversed orderings, and loose string comparisons

Fixes phpstan/phpstan#8231

…oose comparison

- Add handling in `specifyTypesForConstantStringBinaryExpression` to propagate
  type narrowing from `(string)$x === ''` back to `$x`, removing `null`, `false`,
  and `''` from the inner expression's type
- Add handling in `specifyTypesForConstantBinaryExpression` for `(int)` and
  `(float)` casts to remove `null` and `false` when comparing against zero/non-zero
- Extend `resolveIdentical` normalization to also swap `Cast` expressions to the
  left side, matching the existing `FuncCall` normalization
- Add inner expression narrowing in `resolveEqual` for the `== ''` handler when
  the expression is a `Cast\String_`
- `(bool)` cast already works through existing `specifyTypesForConstantBinaryExpression`
  delegation to `specifyTypesInCondition`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants